package lol.clann.minecraft.springboot.api.utils;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.locks.ReentrantLock;

/**
 *
 * 方便的调用回调方法
 *
 * @author pyz
 * @date 2019/5/2 4:40 PM
 */
@Slf4j
public class CallbackUtils {
    private static final ReentrantLock lock = new ReentrantLock();
    private static final ReentrantLock lock2 = new ReentrantLock();

    private static final Map<Class, Map<Class, List<Method>>> callbackCache = new WeakHashMap<>();

    public static void invok(Object bean, Class<? extends Annotation> annotationType, Object... params) {
        for (Method callback : getCallbacks(ClassUtils.getUserClass(bean), annotationType)) {
            try {
                callback.invoke(bean, params);
            } catch (Throwable e) {
                LOGGER.error("fail to invoke callback " + String.valueOf(callback) + " of " + String.valueOf(bean), e);
            }
        }
    }

    public static void invokStatic(Object bean, Class<? extends Annotation> annotationType, Object... params) {
        for (Method callback : getCallbacks(ClassUtils.getUserClass(bean), annotationType)) {
            try {
                callback.invoke(null, params);
            } catch (Throwable e) {
                LOGGER.error("fail to invoke callback " + String.valueOf(callback), e);
            }
        }
    }

    private static List<Method> getCallbacks(Class clazz, Class<? extends Annotation> annotationType) {
        Map<Class, List<Method>> annCache = callbackCache.get(clazz);
        if (annCache == null) {
            lock.lock();
            annCache = callbackCache.computeIfAbsent(clazz, k -> new HashMap<>());
            lock.unlock();
        }
        List<Method> callbacks = annCache.get(annotationType);
        if (callbacks == null) {
            lock2.lock();
            callbacks = annCache.get(annotationType);
            if (callbacks == null) {
                callbacks = new ArrayList<>();
                Method[] ms = ReflectionUtils.getUniqueDeclaredMethods(clazz);
                for (Method m : ms) {
                    if (m.isAnnotationPresent(annotationType)) {
                        m.setAccessible(true);
                        callbacks.add(m);
                    }
                }
                annCache.put(annotationType, callbacks);
            }
            lock2.unlock();
        }
        return callbacks;
    }
}
